package com.fjh.security.easy.starter.config;


import com.fasterxml.jackson.databind.ObjectMapper;
import com.fjh.security.easy.starter.authentication.filter.SecurityCaptchaFilter;
import com.fjh.security.easy.starter.userdetails.CustomUser;
import com.fjh.security.easy.starter.userdetails.EasyDefaultJdbcDaoImpl;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.session.StoreType;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.jackson2.SecurityJackson2Modules;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.security.web.authentication.rememberme.InMemoryTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import org.springframework.session.web.http.CookieHttpSessionIdResolver;
import org.springframework.session.web.http.HeaderHttpSessionIdResolver;
import org.springframework.session.web.http.HttpSessionIdResolver;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;

import javax.annotation.Resource;
import javax.sql.DataSource;

/**
 * @author fanjh37
 * @since 2023/1/31 19:34
 */
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig {


    @Resource
    private AuthenticationSuccessHandler authenticationSuccessHandler;
    @Resource
    private AuthenticationFailureHandler authenticationFailureHandler;
    @Resource
    private AuthenticationEntryPoint authenticationEntryPoint;
    @Resource
    private AccessDeniedHandler accessDeniedHandler;
    @Resource
    private LogoutSuccessHandler logoutSuccessHandler;
    @Resource
    private SecurityCaptchaFilter securityCaptchaFilter;
    @Resource
    private EasyPropertiesConfig easyPropertiesConfig;


    private String[] defaultIgnore = {"/doc.html", "/v2/**", "/webjars/**", "/swagger-resources/**", "/error", "/captchaImage", "/static/**"};


    /**
     * 1、配置登录和拦截
     * 2、新增过滤器
     *
     * @param http
     * @throws Exception
     */
    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity http, PersistentTokenRepository tokenRepository) throws Exception {
        //登录成功
        http.formLogin().successHandler(authenticationSuccessHandler);
        //登录失败
        http.formLogin().failureHandler(authenticationFailureHandler);
        //没有登录权限
        http.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint);
        //没有接口权限 @preAuth
        http.exceptionHandling().accessDeniedHandler(accessDeniedHandler);
        //推出登录
        http.logout().logoutSuccessHandler(logoutSuccessHandler);
        //图像验证码拦截器
        http.addFilterBefore(securityCaptchaFilter, UsernamePasswordAuthenticationFilter.class);
        //忽略路径
        http.authorizeRequests().antMatchers(defaultIgnore).permitAll();
        http.authorizeRequests().antMatchers(easyPropertiesConfig.getIgnoreUrl()).permitAll();
        http.authorizeRequests().anyRequest().authenticated();
        //跨越
        http.cors();
        //伪造登录
        http.csrf().disable();
        http.headers().cacheControl();
        //记住我
        http.rememberMe().tokenRepository(tokenRepository);
        return http.build();
    }

    /**
     * 持久化token 将token持久化到数据库
     * 如果使用JdbcTokenRepositoryImpl，会创建表persistent_logins
     */
    @Bean
    public PersistentTokenRepository tokenRepository(DataSource dataSource) {
        if (StoreType.JDBC.equals(easyPropertiesConfig.getRememberMe().getStoreType())) {
            JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
            // 设置数据源
            tokenRepository.setDataSource(dataSource);
            return tokenRepository;
        }
        return new InMemoryTokenRepositoryImpl();
    }


    /**
     * 跨域访问
     *
     * @return
     */
    @Bean("corsFilter")
    public CorsFilter corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.addAllowedOriginPattern("*");
        corsConfiguration.addAllowedHeader("*");
        corsConfiguration.addAllowedMethod("*");
        corsConfiguration.setAllowCredentials(true);
        source.registerCorsConfiguration("/**", corsConfiguration);
        return new CorsFilter(source);
    }

    /**
     * 用户登录默认
     */
    @Bean
    @ConditionalOnMissingBean
    public UserDetailsService userDetailsService(DataSource dataSource) {
        EasyDefaultJdbcDaoImpl myJdbcDao = new EasyDefaultJdbcDaoImpl();
        myJdbcDao.setDataSource(dataSource);
        if (StoreType.JDBC.equals(easyPropertiesConfig.getUserDetails().getStoreType())) {
            return myJdbcDao;
        } else {
            InMemoryUserDetailsManager memoryUserDetailsManager = new InMemoryUserDetailsManager();
            for (int i = 0; i < easyPropertiesConfig.getUserDetails().getUsers().size(); i++) {
                CustomUser user = easyPropertiesConfig.getUserDetails().getUsers().get(i);
                user.setPassword("{noop}" + user.getPassword());
                memoryUserDetailsManager.createUser(user);
            }
            return memoryUserDetailsManager;
        }

    }

    /**
     * spring session 序列化
     */
    @Bean
    public RedisSerializer<Object> springSessionDefaultRedisSerializer() {
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.registerModules(SecurityJackson2Modules.getModules(getClass().getClassLoader()));
        return new GenericJackson2JsonRedisSerializer(objectMapper);
    }

    /**
     * 请求头 sessionId
     */
    @Bean
    public HttpSessionIdResolver sessionIdResolver(EasyPropertiesConfig easyPropertiesConfig) {
        if (easyPropertiesConfig.getSession().isCookieType()) {
            return new CookieHttpSessionIdResolver();
        }
        return new HeaderHttpSessionIdResolver(easyPropertiesConfig.getSession().getHeaderName());
    }


    /**
     * 开启是否提示用户名不存在
     *
     * @param userDetailsService
     * @return
     */
    @Bean
    public AuthenticationProvider daoAuthenticationProvider(UserDetailsService userDetailsService) {
        DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
        daoAuthenticationProvider.setUserDetailsService(userDetailsService);
        daoAuthenticationProvider.setHideUserNotFoundExceptions(false);
        return daoAuthenticationProvider;
    }


}